[기능구현#1] 패스워드 검증 추가하기

실행 환경

같은 폴더의 “[리팩터링] 게시판 프로젝트 아키텍처” 노트에 명시된 스택 기준 — Spring Boot 3.3.5, Java 17, H2 Database, Thymeleaf.

  1. 직접 검증
  2. BindingResult
  3. 애노테이션 사용

직접검증

  • HashMap을 이용하여, error를 담고 ModelAttribute로 반환
  • thymeleaf의 조건문과 삼항문을 이용하여, error 인 경우와 정상인경우 렌더링
//[Controller]
 
       Map<String,String> errors = new HashMap<>();
       
       if ( member.getPasswd() != null && member.getPasswd().length() >= 8 ){
            if (member.getPasswd().matches("[a-zA-Z]+")){
                errors.put("passwd", "비밀번호는 숫자와 특수문자를 포함해야합니다.");
            }else if (member.getPasswd().matches("[0-9]+")) {
                errors.put("passwd", "비밀번호는 문자와 특수문자를 포함해야합니다.");
            }else if (member.getPasswd().matches("[a-zA-Z0-9]+")){
                errors.put("passwd", "비밀번호는 특수문자를 포함해야합니다.");
            }
        }else{
            errors.put("passwd", "비밀번호는 문자,숫자,특수문자를 포함하여 8자이상이어야합니다.");
        }
        
        
        if(!errors.isEmpty()){
            model.addAttribute("errors",errors);
            return "member/member-form";
        }

[Thymeleaf]

<input 태그>
th:class=”${errors?.containsKey(‘[모델attribute값]’)} ? ‘[오류형태]’ : ‘[정상형태]‘“

th:if = "${errors?.containsKey('[모델 attribute값]')}" th:text="${errors'[모델 attribute값]'}" (* th:if 는 model.addAttribute로 값이 넘어왔는지 확인) (* th:text는 값이 넘어왔을경우, 해당 MAP에 기록되어있는 오류메시지) ```html
비밀번호 입력 오류
```

BindingResult

  • BindingResult는 @ModelAttribute 뒤에 바로 인자값으로 들어와야함 (spring 인지 가능)
    • BindingResult의 경우, Model에 자동으로 포함됨
  • FieldError를 통해서, 에러값 넣기 (HashMap역할)
    • FieldError(String ObjectName, String Field, String DefaultMessage) {}
      • objectName : @ModelAttribute 이름
      • field : 오류가 발생한 필드 이름
      • defaultMessage : 오류 기본 메시지
    • FieldError(String ObjectName, String field, Object rejectedValue, boolean bindingFailure, @Nullable String[] codes, @Nullable Object[] argument, @Nullable String defaultMessage(
      • ObjectName : 오류가 발생한 객체 이름
      • field : 오류 필드
      • rejectedValue : 사용자가 입력한 값
      • bindingFailure : 타입 오류 같은 바인딩 실패인지 검증실패인지 구분값
      • codes : 메시지 코드
      • argument : 메시지에서 사용하는 인자
      • defaultMessage : 기본 오류 메시지
    • 추가) ObjectError(String objectName, String DefaultMessage){}
      • objectName : @ModelAttribute 이름
      • defaultMessage : 오류기본메시지
      • 특정필드 이외의 오류가 필요한경우 사용
  • thymeleaf의 th:errorclass 및 th:errors를 이용하여 표현
public String registryMember(@ModelAttribute Member member, BindingResult bindingResult){       
 
//...
       if ( member.getPasswd() != null && member.getPasswd().length() >= 8 ){
            if (member.getPasswd().matches("[a-zA-Z]+")){
                bindingResult.addError(new FieldError("member","passwd", "비밀번호는 숫자와 특수문자를 포함해야합니다."));
            }else if (member.getPasswd().matches("[0-9]+")) {
                bindingResult.addError(new FieldError("member", "passwd", "비밀번호는 문자와 특수문자를 포함해야합니다"));
            }else if (member.getPasswd().matches("[a-zA-Z0-9]+")){
                bindingResult.addError(new FieldError("member","passwd", "비밀번호는 특수문자를 포함해야합니다 "));
            }
        }else{
            bindingResult.addError(new FieldError("member","passwd", "비밀번호는 문자,숫자,특수문자를 포함하여 8자이상이어야합니다."));
        }
 
        if (bindingResult.hasErrors()){
            return "member/member-form";
        }
        
//... 성공시나리오
}

th:errors : 해당 필드에 오류가 있는경우 태그 출력 (th:if의 편의 버전)
th:errorclass : th:field에서 지정한 필드에 오류가 있으면 class 정보를 추가함

<div class="form-group mb-3">
     <label for="passwd">Password:</label>
     <input type="password" id="passwd" th:field="*{passwd}" th:errorclass="field-error" class="form-control">
     <div class="field-error" th:errors="*{passwd}"> 비밀번호 입력 오류 </div>
</div>

애노테이션 사용

  • 검증기 직접 생성방법
public class BindigResultValidate implements Validator {
    @Override
    public boolean supports(Class<?> clazz) {
        return Member.class.isAssignableFrom(clazz);
    }
 
    @Override
    public void validate(Object target, Errors errors) {
 
        Member member = (Member) target;
        BindingResult bindingResult = (BindingResult) errors;
 
        //로그인ID
        if ( !StringUtils.hasText(member.getLoginId())){
            bindingResult.addError(new FieldError("member", "loginId","로그인 아이디는 필수입력입니다."));
        }
 
        //이름
        if (!StringUtils.hasText(member.getName())){
            bindingResult.addError(new FieldError("member", "name", "이름은 필수 입력값입니다."));
        }
 
        //비밀번호
        if ( member.getPasswd() != null && member.getPasswd().length() >= 8 ){
            if (member.getPasswd().matches("[a-zA-Z]+")){
                bindingResult.addError(new FieldError("member","passwd", "비밀번호는 숫자와 특수문자를 포함해야합니다."));
            }else if (member.getPasswd().matches("[0-9]+")) {
                bindingResult.addError(new FieldError("member", "passwd", "비밀번호는 문자와 특수문자를 포함해야합니다"));
            }else if (member.getPasswd().matches("[a-zA-Z0-9]+")){
                bindingResult.addError(new FieldError("member","passwd", "비밀번호는 특수문자를 포함해야합니다 "));
            }
        }else{
            bindingResult.addError(new FieldError("member","passwd", "비밀번호는 문자,숫자,특수문자를 포함하여 8자이상이어야합니다."));
        }
 
    }
}
@PostMapping("/form")
    public String registryMember(@ModelAttribute Member member, BindingResult bindingResult, Model model){
        
        //생성자 주입으로 넣기
        bindingResultValidate.validate(member,bindingResult);
 
        if (bindingResult.hasErrors()){
            return "member/member-form";
        }
 
        memberRepository.save(member);
        return "redirect:/members/login";
    }
  • @Validated로 적용
    • bindingResultValidate 미리 등록
      • @InitBinder -> WebDataBinder로 validate 등록
    • @Validated 를 @ModelAttribute앞에 추가
@InitBinder
private void init(WebDataBinder webDataBinder){    
    webDataBinder.addValidators(bindingResultValidate);
}
 
@PostMapping("/form")
    public String registryMember(@Validated @ModelAttribute Member member, BindingResult bindingResult, Model model){
 
        if (bindingResult.hasErrors()){
            return "member/member-form";
        }
 
        memberRepository.save(member);
        return "redirect:/members/login";
    }

관련 문서